一步一步DIY jQuery库2-使用es6模块化

本博文使用了rollup打包,这里同时提供了简明的搭建环境的说明,通过第一部分1.环境搭建就可以在本地配置搭建环境。有关rollup的详细安装使用说明可以查看我的另外一篇博客:《rollup + es6最佳实践》

我们首先把《一步一步DIY一个自己jQuery库1》的代码使用es6模块化的方式打包好

【注】所有代码挂在我的github

1.搭建环境

1.1 目录结构

1
2
3
4
5
6
7
8
9
10
11
- src
+ .babelrc
+ core.js
+ global.js
+ init.js
+ jquery.js
+ util.js
bundle.js
package.json
rollup.config.dev.js
test.html
  • src是源代码文件夹,其中jquery.js是入口文件
  • bundle是编译后的文件
  • package.json是包管理文件
  • rollup.config.dev.js是rollup的配置文件
  • test.html是测试文件,引入<script src="bundle.js"></script>即可测试

1.2 npm安装

1
npm i rollup rollup-plugin-babel babel-preset-es2015-rollup --save-dev

1.3 使用配置编译

新建文件,文件名为rollup.config.dev.js

1
2
3
4
5
6
7
8
9
import babel from 'rollup-plugin-babel';

export default {
entry: 'src/jquery.js',
format: 'umd',
moduleName: 'jQuery',
plugins: [babel() ],
dest: 'bundle.js',
};

src中.babelrc

1
2
3
4
5
{
presets: [
["es2015", { "modules": false }]
]
}

注意{ "modules": false }一定要有,否则一直报错
执行命令:rollup -c rollup.config.dev.js,就能得到编译后的文件bundle.js。这里使用的是【umd】的形式,这是jquery的发布版本的格式,当然还有其他的一些格式,amd / es6 / cjs / iife

2.打包

jquery.js

1
2
3
4
5
6
7
8
9
// 出口
import jQuery from './core';
import global from './global';
import init from './init';

global(jQuery);
init(jQuery);

export default jQuery;

core.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
var version = "0.0.1",
jQuery = function(selector, context) {
return new jQuery.fn.init(selector, context);
};

jQuery.fn = jQuery.prototype = {
jquery: version,
constructor: jQuery,
setBackground: function() {
this[0].style.background = 'yellow';
return this;
},
setColor: function() {
this[0].style.color = 'blue';
return this;
}
};

jQuery.extend = jQuery.fn.extend = function() {
var isObject = function(obj) {
return Object.prototype.toString.call(obj) === "[object Object]";
};
var isArray = function(obj) {
return Object.prototype.toString.call(obj) === "[object Array]";
};
var name, clone, copy, copyIsArray ,options, i = 1,
length = arguments.length,
target = arguments[0] || {},
deep = false; //默认为浅复制

if (typeof target === "boolean") {
deep = target;
target = arguments[i] || {};
i++;
}
if (typeof target !== "object" && typeof target !== "function") {
target = {};
}

//target后面没有其他参数了(要拷贝的对象),直接扩展jQuery自身,target并入jQuery
if (i === length) {
target = this;
i--;
}
for (; i < length; i++) {
if ((options = arguments[i]) != null) {
for (name in options) {
src = target[name]; //jQuery是否已经有该属性
copy = options[name];
if (target === copy) {
continue;
}
//深拷贝,且确保被拷属性为对象/数组
if (deep && copy && isObject(copy) || (copyIsArray = isArray(copy))) {
//被拷贝属性为数组
if (copyIsArray) {
copyIsArray = false;
//被合并属性
clone = src && isArray(src) ? src : [];
} else { //被拷贝属性为对象
clone = src && isObject(src) ? src : {};
}
//右侧递归,直到内部属性值是非对象
target[name] = jQuery.extend(deep, clone, copy);
} else if (copy !== undefined) { //非对象/数组,或者浅复制的情况
target[name] = copy; //递归结束
}
}
}
}
//返回修改后的target
return target;
};
export default jQuery;

init.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var init = function(jQuery){
jQuery.fn.init = function (selector, context, root) {
if (!selector) {
return this;
} else {
var elem = document.querySelector(selector);
if (elem) {
this[0] = elem;
this.length = 1;
}
return this;
}
};

jQuery.fn.init.prototype = jQuery.fn;
};
export default init;

global.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var global = function(jQuery){
//走模块化形式的直接绕过
if(typeof exports === 'object'&&typeof module !== 'undefined') return;
var _jQuery = window.jQuery,
_$ = window.$;
jQuery.noConflict = function( deep ) {
//确保window.$没有再次被改写
if ( window.$ === jQuery ) {
window.$ = _$;
}
//确保window.jQuery没有再次被改写
if ( deep&&window.jQuery === jQuery ) {
window.jQuery = _jQuery;
}
return jQuery; //返回 jQuery 接口引用
};

window.jQuery = window.$ = jQuery;
};

export default global;

打包后bundle.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.jQuery = factory());
}(this, (function () { 'use strict';

var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };

var version = "0.0.1";
var jQuery$1 = function jQuery$1(selector, context) {

return new jQuery$1.fn.init(selector, context);
};

jQuery$1.fn = jQuery$1.prototype = {
jquery: version,
constructor: jQuery$1,
setBackground: function setBackground() {
this[0].style.background = 'yellow';
return this;
},
setColor: function setColor() {
this[0].style.color = 'blue';
return this;
}
};

jQuery$1.extend = jQuery$1.fn.extend = function () {
var isObject = function isObject(obj) {
return Object.prototype.toString.call(obj) === "[object Object]";
};
var isArray = function isArray(obj) {
return Object.prototype.toString.call(obj) === "[object Array]";
};
var name, clone, copy, copyIsArray, options,
i = 1,
length = arguments.length,
target = arguments[0] || {},
deep = false; //默认为浅复制

if (typeof target === "boolean") {
deep = target;
target = arguments[i] || {};
i++;
}
if ((typeof target === 'undefined' ? 'undefined' : _typeof(target)) !== "object" && typeof target !== "function") {
target = {};
}

//target后面没有其他参数了(要拷贝的对象),直接扩展jQuery自身,target并入jQuery
if (i === length) {
target = this;
i--;
}
for (; i < length; i++) {
if ((options = arguments[i]) != null) {
var name, clone, copy;
for (name in options) {
src = target[name]; //jQuery是否已经有该属性
copy = options[name];
if (target === copy) {
continue;
}
//深拷贝,且确保被拷属性为对象/数组
if (deep && copy && isObject(copy) || (copyIsArray = isArray(copy))) {
//被拷贝属性为数组
if (copyIsArray) {
copyIsArray = false;
//被合并属性
clone = src && isArray(src) ? src : [];
} else {
//被拷贝属性为对象
clone = src && isObject(src) ? src : {};
}
//右侧递归,直到内部属性值是非对象
target[name] = jQuery$1.extend(deep, clone, copy);
} else if (copy !== undefined) {
//非对象/数组,或者浅复制的情况
target[name] = copy; //递归结束
}
}
}
}

//返回修改后的target

return target;
};

var _typeof$1 = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };

var global = function global(jQuery) {
//走模块化形式的直接绕过
if ((typeof exports === 'undefined' ? 'undefined' : _typeof$1(exports)) === 'object' && typeof module !== 'undefined') return;

var _jQuery = window.jQuery,
_$ = window.$;

jQuery.noConflict = function (deep) {
//确保window.$没有再次被改写
if (window.$ === jQuery) {
window.$ = _$;
}

//确保window.jQuery没有再次被改写
if (deep && window.jQuery === jQuery) {
window.jQuery = _jQuery;
}

return jQuery; //返回 jQuery 接口引用
};

window.jQuery = window.$ = jQuery;
};

var init = function init(jQuery) {
jQuery.fn.init = function (selector, context, root) {
if (!selector) {
return this;
} else {
var elem = document.querySelector(selector);
if (elem) {
this[0] = elem;
this.length = 1;
}
return this;
}
};

jQuery.fn.init.prototype = jQuery.fn;
};

// 出口
global(jQuery$1);
init(jQuery$1);

return jQuery$1;

})));

3.增加基础工具模块&完善extend方法

我们在《一步一步DIY一个自己jQuery库1》中说过extend方法的不完善的地方

  • 使用isObject,isArray并不严谨。在某些浏览器中,像 document 在 Object.toSting 调用时也会返回和 Object 相同结果;

新增一个util.js

1
2
3
4
5
6
export var class2type = {}; //在core.js中会被赋予各类型属性值
export const toString = class2type.toString; //等同于 Object.prototype.toString
export const getProto = Object.getPrototypeOf;
export const hasOwn = class2type.hasOwnProperty;
export const fnToString = hasOwn.toString; //等同于 Object.toString/Function.toString
export const ObjectFunctionString = fnToString.call(Object); //顶层Object构造函数字符串"function Object() { [native code] }",用于判断 plainObj

core.js中修改/新增代码

  • 导入
1
import { class2type, toString, getProto, hasOwn, fnToString, ObjectFunctionString } from './util.js';
  • 修改
1
2
3
typeof target !== "function"   //修改为!jQuery.isFunction(target)
isArray //均修改为jQuery.isArray
isObject //均修改为jQuery.isObject
  • 新增
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//新增修改点1,class2type注入各JS类型键值对,配合 jQuery.type 使用,后面会用上
"Boolean Number String Function Array Date RegExp Object Error Symbol".split(" ").forEach(function(name) {
class2type["[object " + name + "]"] = name.toLowerCase();
});
//新增修改点2
jQuery.extend({
isArray: Array.isArray || function( obj ) {
return jQuery.type(obj) === "array";
},
isFunction: function( obj ) {
return jQuery.type(obj) === "function";
},
isPainObject: function(obj) {
var proto, Ctor;

if (!obj || toString.call(obj) !== "[object Object]") {
return false;
}

proto = getProto(obj);
// 通过 Object.create( null ) 形式创建的 {} 是没有prototype的
if (!proto) {
return true;
}

//简单对象的构造函数等于最顶层Object构造
Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;
},

type: function(obj) {
if (obj == null) { //不能用 ===
return obj + ""; //undefined or null
}

return typeof obj === "object" || typeof obj === "function" ?
//兼容安卓2.3- 函数表达式类型不正确情况
class2type[toString.call(obj)] || "object" :
typeof obj;
}
});

修改后的文件我已经挂在了我的github中,对应文件夹是v2.

参考阅读: